import {ComponentPropsOptions, ExtractPropTypes, SetupContext, getCurrentInstance, defineComponent, provide, inject} from 'vue';
import {ComponentEvent, getComponentEmit, useEvent} from './useEvent'

export function designComponent<
  PropsOptions extends Readonly<ComponentPropsOptions>,
  Props extends Readonly<ExtractPropTypes<PropsOptions>>,
  // TODO 这个类型是什么，为啥返回 boolean
  Emits extends { [k: string]: (...args: any[]) => boolean },
  Refer
  >(options: {
  name?: string,
  props?: PropsOptions,
  provideRefer?: boolean,
  emits?: Emits,
  setup: (parameter: { props: Props, event: ComponentEvent<Emits>, setupContext: SetupContext }) => {
    refer?: Refer,
    render: () => any
  }
}) {
  const { setup, provideRefer, emits, ...leftOptions } = options
  return {
    ...defineComponent({
      ...leftOptions,
      emits: getComponentEmit(emits),
      setup(props: Props, setupContext: SetupContext) {
        const ctx = getCurrentInstance()!
        const event = useEvent<Emits>(emits!)

        if (!setup) {
          console.error('designComponent: setup is required!')
          return () => null
        }
        const {refer, render} = setup({props, event, setupContext})
        if (!!refer) {
          const duplicateKey = Object.keys(leftOptions.props || {})
            .find(i => Object.prototype.hasOwnProperty.call(refer as any, i))
            if (!!duplicateKey) {
              console.error(`designComponent: duplicate key ${duplicateKey}` in refer);
            } else {
              Object.assign(ctx.proxy, refer)
            }
        }
        if (provideRefer) {
          if (!leftOptions.name) {
            console.error('designComponent: name is required when provideRefer is true!')
          } else {
            provide(`@@${leftOptions.name}`, refer)
          }
        }
        return render;
      }
    } as any),
    use: {
      ref: (refName: string) => {
        const ctx = getCurrentInstance()!
        return {
          get value() {
            return ctx.refs[refName] as Refer | null
          }
        }
      },
      inject: (defaultValue?: any) => {
        return inject(`@@${leftOptions.name}`, defaultValue) as Refer
      }
    }
  }
}

